//Modified from opencv 2.1
//by Chen Feng (cforrest at umich.edu)
#include "opencv2/opencv.hpp"

using namespace cv;
class PhonyFernClassifier
{
public:
	PhonyFernClassifier();
	virtual ~PhonyFernClassifier();
	virtual void trainFromSingleView(const Mat& image,
		const vector<KeyPoint>& keypoints,
		int _patchSize=PATCH_SIZE,
		int _signatureSize=DEFAULT_SIGNATURE_SIZE,
		int _nstructs=DEFAULT_STRUCTS,
		int _structSize=DEFAULT_STRUCT_SIZE,
		int _nviews=DEFAULT_VIEWS,
		int _compressionMethod=COMPRESSION_NONE,
		const PatchGenerator& patchGenerator=PatchGenerator());
	virtual int operator()(const Mat& img, Point2f kpt, vector<float>& signature) const;
	virtual int operator()(const Mat& patch, vector<float>& signature) const;
	virtual void clear();
	void setVerbose(bool verbose);

	struct Feature
	{
		uchar x1, y1, x2, y2;
		Feature() : x1(0), y1(0), x2(0), y2(0) {}
		Feature(int _x1, int _y1, int _x2, int _y2)
			: x1((uchar)_x1), y1((uchar)_y1), x2((uchar)_x2), y2((uchar)_y2)
		{}
		template<typename _Tp> bool operator ()(const Mat_<_Tp>& patch) const
		{ return patch(y1,x1) > patch(y2, x2); }
	};

	enum
	{
		PATCH_SIZE = 31,
		DEFAULT_STRUCTS = 50,
		DEFAULT_STRUCT_SIZE = 9,
		DEFAULT_VIEWS = 5000,
		DEFAULT_SIGNATURE_SIZE = 176,
		COMPRESSION_NONE = 0,
		COMPRESSION_RANDOM_PROJ = 1,
		COMPRESSION_PCA = 2,
		DEFAULT_COMPRESSION_METHOD = COMPRESSION_NONE
	};

protected:
	//randomly select feature positions
	virtual void prepare(int _nclasses, int _patchSize, int _signatureSize,
		int _nstructs, int _structSize,
		int _nviews, int _compressionMethod);
	virtual void finalize(RNG& rng);
	virtual int getLeaf(int fidx, const Mat& patch) const;
public:
	bool verbose;
	int nstructs; // number of ferns
	int structSize; // number of features per fern
	int nclasses;
	int signatureSize;
	int compressionMethod;
	int leavesPerStruct;
	Size patchSize;

	vector<Feature> features;
	vector<int> classCounters;
	vector<float> posteriors; // may need to map to int
};

class PhonyPlanarObjectDetector
{
public:
	PhonyPlanarObjectDetector();
	virtual ~PhonyPlanarObjectDetector();
	virtual void train(const Mat& pyr, const vector<KeyPoint>& keypoints,
		int _patchSize=PhonyFernClassifier::PATCH_SIZE,
		int _nstructs=PhonyFernClassifier::DEFAULT_STRUCTS,
		int _structSize=PhonyFernClassifier::DEFAULT_STRUCT_SIZE,
		int _nviews=PhonyFernClassifier::DEFAULT_VIEWS,
		const PatchGenerator& patchGenerator=PatchGenerator());
	Rect getModelROI() const {return modelROI;}
	vector<KeyPoint> getModelPoints() ;
	const PhonyFernClassifier& getClassifier() const {return fernClassifier;}
	void setVerbose(bool verbose);

	bool read_binary_detector(string path);
	bool write_binary_detector(string path) const;
	//bool operator()(const Mat& image, Mat& H, vector<Point2f>& corners) const;
	//bool operator()(const Mat& pyr, const vector<KeyPoint>& keypoints,
	//	Mat& H, vector<Point2f>& corners, vector<int>* pairs=0) const;
	bool operator()(
		const Mat& pyr, const vector<KeyPoint>& keypoints,
		vector<Point2f>& toPt,
		Mat& matH, vector<Point2f>& corners, vector<int>* pairs=0) const;

	//do not need H
	bool operator()(const Mat& pyr, const vector<KeyPoint>& keypoints,
		vector<int>* pairs, float thresh) const;

protected:
	bool verbose;
	Rect modelROI;
	vector<KeyPoint> modelPoints;
	PhonyFernClassifier fernClassifier;
};
